<?php
/**
 * Created by PhpStorm.
 * User: zsf
 * Date: 2018/8/7
 * Time: 13:05
 */

namespace App\Services\Authorize;

use App\Exceptions\ApiException;
use App\Services\ResponseCode;
use App\Services\VerifyCode;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Illuminate\Support\Facades\Log;

abstract class BaseSignature
{

    const TIME_DEVIATION = 5 * 60;//请求时间误差允许范围，5分钟

    abstract protected function getSignatureHeadKeys();

    abstract protected function getSignatureKey();

    /**
     * @throws \App\Exceptions\ApiException
     * 校验请求时间是否在允许范围内
     */
    protected function _validateTimestamp(): void
    {
        if (!setting('timestamp_validate', true)) {
            return;
        }
        $time = (int)request()->header('timestamp', 0);
        if (abs($time - time()) > self::TIME_DEVIATION) {
            api_exception(ResponseCode::REQUEST_TIME_IS_EXPIRED, ['seconds' => self::TIME_DEVIATION]);
        }
    }

    /**
     * @return bool
     * 校验签名是否匹配
     */
    protected function validateSignature(): bool
    {
        if (!setting('signature_validate', true)) {
            return true;
        }
        $signature = $this->getSignature();
        $headerSignature = request()->header('signature');
        $this->validateMaxLoginTimes();
        if (strtoupper($headerSignature) != $signature) {
            $this->incrementFailedLoginTimes();
            if(is_pc()){
                throw new ApiException('账号或密码错误', ResponseCode::SIGNATURE_ERROR);
            }
            $signature = config('app.debug') ? $signature : '';
            api_exception(ResponseCode::SIGNATURE_ERROR,'账号或密码错误' . $signature);
        }
        $this->clearLoginFailedTimes();
        return true;
    }

    /**
     * @return $this
     * @throws \Exception
     * 清除登录失败次数记录
     */
    protected function clearLoginFailedTimes()
    {
        $key = $this->getMaxFailedLoginTimesCacheKey();
        if (!is_null($key)) {
            cache()->forget($key);
        }
        return $this;
    }

    /**
     * @return $this
     * @throws \Exception
     * 增加当前用户失败登录次数
     */
    protected function incrementFailedLoginTimes()
    {
        $key = $this->getMaxFailedLoginTimesCacheKey();
        if (!is_null($key)) {
            $loginFailedTimes = intval(cache($key));
            cache()->put($key, $loginFailedTimes + 1, 1440);
        }
        return $this;
    }

    /**
     * @return string|null
     * 获取最大登录次数缓存key
     */
    protected function getMaxFailedLoginTimesCacheKey()
    {
        if (get_called_class() == 'App\Services\Authorize\Signature' || get_called_class() == 'App\Services\Authorize\SeatSignature' || get_called_class() == 'App\Services\Authorize\RecodingDownloadSignature') {
            if (get_called_class() == 'App\Services\Authorize\Signature' || get_called_class() == 'App\Services\Authorize\RecodingDownloadSignature') {
                $keyPrefix = 'user:loginfailed:';
                $user = $this->user;
            } else {
                $keyPrefix = 'seat:loginfailed:';
                $user = $this->seat;
            }
            return $keyPrefix . $user['id'];
        }
        return null;
    }


    /**
     * 校验验证码
     * @throws ApiException
     */
    protected function verifyCaptcha() {
        if (get_called_class() == 'App\Services\Authorize\Signature' || get_called_class() == 'App\Services\Authorize\SeatSignature' || get_called_class() == 'App\Services\Authorize\RecodingDownloadSignature') {
            $isVerify = env('IS_VERIFY_CAPTCHA', 1);
            // PC标准版不校验验证码
            if ($isVerify && request()->header('source') != 'client.pc.common') {
                $verify = new VerifyCode(request()->input('session_id'));
                if (!$verify->verify(request()->input('verify_code'))) {
                    api_exception(ResponseCode::CAPTCHA_ERROR);
                }
            }
        }
    }


    /**
     * @throws \App\Exceptions\ApiException
     * 校验最大失败登录次数
     */
    protected function validateMaxLoginTimes()
    {
        $key = $this->getMaxFailedLoginTimesCacheKey();
        if (!is_null($key)) {
            $loginFailedTimes = intval(cache($key));
            $maxLoginTimes = env('USER_MAX_LOGIN_TIMES', 10);
            if ($loginFailedTimes >= $maxLoginTimes) {
                api_exception(is_pc() || is_bs() ? ResponseCode::IS_REACH_MAX_LOGIN_TIMES_BS_OR_PC : ResponseCode::IS_REACH_MAX_LOGIN_TIMES);
            }
        }
        return $this;
    }

    /**
     * @return string
     * 计算签名
     */
    protected function getSignature(): string
    {
        $requests = $this->filterEmpty(request()->all());
        foreach ($requests as $key => $request) {
            if ($request instanceof UploadedFile) {
                $requests[$key] = md5_file($request);
            }
        }
        $headers = collect(request()->header())->only(static::getSignatureHeadKeys())->toArray();
        foreach ($headers as $key => $header) {
            $headers[$key] = current($header);
        }
        $headers = $this->filterEmpty($headers);
        $parameters = array_merge($requests, $headers);
        ksort($parameters);
        $parameters['signatureKey'] = static::getSignatureKey();
        $string = [];
        foreach ($parameters as $key => $parameter) {
            $string[] = $key . '=' . $parameter;
        }
        $string = implode('&', $string);
        Log::debug($string);
        return strtoupper(hash('sha256', $string));
    }

    /**
     * @param array $array
     * @return array
     * 过滤值为空字符串的参数
     */
    protected function filterEmpty(array $array)
    {
        foreach ($array as $key => $value) {
            if ($value === '') {
                unset($array[$key]);
            }
        }
        return $array;
    }
}
